package com.epigeon.amadeo.engine;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import android.content.Context;

import com.epigeon.amadeo.input.ActiveInput;
import com.epigeon.amadeo.input.Input;
import com.epigeon.amadeo.input.PassiveInput;
import com.epigeon.amadeo.modifier.Modifier;
import com.epigeon.amadeo.output.Output;

public class EngineConfiguration {
	
	// XML tags
	private static final String XML_ROOT_LABEL = "AmadeoEngineConfiguration";
	private static final String XML_PASSIVE_INPUT_LABEL = "PassiveInput";
	private static final String XML_PASSIVE_INPUTS_LABEL = "PassiveInputs";
	private static final String XML_ACTIVE_INPUT_LABEL = "ActiveInput";
	private static final String XML_ACTIVE_INPUTS_LABEL = "ActiveInputs";
	private static final String XML_OUTPUT_LABEL = "Output";
	private static final String XML_OUTPUTS_LABEL = "Outputs";
	private static final String XML_MODIFIER_LABEL = "Modifier";
	private static final String XML_MODIFIERS_LABEL = "Modifiers";
	private static final String XML_ROUTE_LABEL = "Route";
	private static final String XML_ROUTES_LABEL = "Routes";
	private static final String XML_PARAMETER_LABEL = "Parameter";
	private static final String XML_PARAMETERS_LABEL = "Parameters";	
	
	// XML attributes
	private static final String XML_KEY_ATTRIBUTE = "key";
	private static final String XML_PARAMETERKEYS_ATTRIBUTE = "parameterKeys";
	private static final String XML_INPUTKEY_ATTRIBUTE = "inputKey";
	private static final String XML_VALUE_ATTRIBUTE = "value";
	
	// Object Maps
	private Map<String, Input> inputMap;
	private Map<String, Output> outputMap;
	private Map<String, Modifier> modifierMap;
	
	private Map<String, List<String>> passiveInputMappingMap;
	private Map<String, String> parametersMap;
	private Map<String, String> parametersMappingMap;
	
	private Map<String, List<Route>> inputRoutesMapping;
	private Map<String, List<Route>> outputRoutesMapping; 
	private Map<String, List<Route>> modifierRoutesMapping;
	private Map<String, List<String>> modifierOutputsMapping;
	
	// Routes List
	private List<Route> routes;
	
	private Context context;
	private int xmlResourceID;
		
	public EngineConfiguration(Context context, int xmlResourceID) {
		
		this.inputMap = new HashMap<String, Input>();
		this.outputMap = new HashMap<String, Output>();
		this.modifierMap = new HashMap<String, Modifier>();
		this.parametersMap = new HashMap<String, String>();
		
		this.passiveInputMappingMap = new HashMap<String, List<String>>();
		this.parametersMappingMap = new HashMap<String, String>();
		this.inputRoutesMapping = new HashMap<String, List<Route>>();
		this.modifierRoutesMapping = new HashMap<String, List<Route>>();
		this.outputRoutesMapping = new HashMap<String, List<Route>>();
		this.modifierOutputsMapping = new HashMap<String, List<String>>();
		
		this.routes = new LinkedList<Route>();
		
		this.context = context;
		this.xmlResourceID = xmlResourceID;
		
		this.loadConfiguration();
		
	}
	
	private void loadConfiguration() {
		try {
			
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse( context.getResources().openRawResource(xmlResourceID) );
			
			
			Node root = doc.getFirstChild();
			// check if the top root name is ok
			if ( XML_ROOT_LABEL.equals(root.getNodeName()) == false ) {
				throw new EngineException(EngineException.INVALID_ROOT_EXCEPTION);
			}
			
			Node child = root.getFirstChild();
			
			while ( child != null ) {
				if ( XML_PASSIVE_INPUTS_LABEL.equals(child.getNodeName()) ) {
					// load passive inputs keys 
					Node child2 = child.getFirstChild();
					while ( child2 != null ) {
						if ( XML_PASSIVE_INPUT_LABEL.equals(child2.getNodeName())) {
							// load passive input
							NamedNodeMap attributes = child2.getAttributes();
							Node key = attributes.getNamedItem(XML_KEY_ATTRIBUTE);
							Node params = attributes.getNamedItem(XML_PARAMETERKEYS_ATTRIBUTE);
							
							/* 
							 * get the key and add it to the input map with null as reference
							 * which will be later replaced by registering the input from
							 * outside the engine
							 */
							if ( key == null ) {
								throw new EngineException(EngineException.INPUT_NO_KEY_EXCEPTION);
							}
							key.getNodeValue();
							inputMap.put(key.getNodeValue(), null);
							
							/*
							 * check if any parameters are specified and add them to the
							 * mapping map :), this parameters will be loaded by the object
							 * upon registration
							 */
							if ( params != null && "".equals(params.getNodeValue()) == false) {
								parametersMappingMap.put(key.getNodeValue(), params.getNodeValue());
							}
						}
						child2 = child2.getNextSibling();
					}
				} else if ( XML_ACTIVE_INPUTS_LABEL.equals(child.getNodeName()) ) {
					// load active inputs keys 
					Node child2 = child.getFirstChild();
					while ( child2 != null ) {
						if ( XML_ACTIVE_INPUT_LABEL.equals(child2.getNodeName())) {
							// load active input
							NamedNodeMap attributes = child2.getAttributes();
							Node key = attributes.getNamedItem(XML_KEY_ATTRIBUTE);
							Node params = attributes.getNamedItem(XML_PARAMETERKEYS_ATTRIBUTE);
							
							/* 
							 * get the key and add it to the input map with null as reference
							 * which will be later replaced by registering the input from
							 * outside the engine
							 */
							if ( key == null ) {
								throw new EngineException(EngineException.INPUT_NO_KEY_EXCEPTION);
							}
							key.getNodeValue();
							inputMap.put(key.getNodeValue(), null);
							
							/*
							 * check if any parameters are specified and add them to the
							 * mapping map :), this parameters will be loaded by the object
							 * upon registration
							 */
							if ( params != null && "".equals(params.getNodeValue()) == false) {
								parametersMappingMap.put(key.getNodeValue(), params.getNodeValue());
							}
							
							/*
							 * load the active -> passive input mapping
							 */
							Node child3 = child2.getFirstChild();
							if ( XML_PASSIVE_INPUTS_LABEL.equals(child3.getNodeName())) {
								child3 = child3.getFirstChild();
								while ( child3 != null ) {
									
									NamedNodeMap attributes2 = child3.getAttributes();
	
									Node key2 = attributes2.getNamedItem(XML_KEY_ATTRIBUTE);
									if ( key2 != null && "".equals(key2.getNodeValue()) == false) {
										
										if ( passiveInputMappingMap.containsKey(key2.getNodeValue()) == false ) {
											passiveInputMappingMap.put(key2.getNodeValue(), new LinkedList<String>());										
										}
										passiveInputMappingMap.get(key2.getNodeValue()).add(key.getNodeValue());
	
									} else {
										throw new EngineException(EngineException.INPUT_NO_KEY_EXCEPTION);
									}
									
									child3 = child3.getNextSibling();
								}
							}
						}
						child2 = child2.getNextSibling();
					}					
				} else if ( XML_MODIFIERS_LABEL.equals(child.getNodeName()) ) {
					Node child2 = child.getFirstChild();
					while ( child2 != null ) {
						if ( XML_MODIFIER_LABEL.equals(child2.getNodeName())) {
							// load modifier
							NamedNodeMap attributes = child2.getAttributes();
							Node key = attributes.getNamedItem(XML_KEY_ATTRIBUTE);
							
							modifierMap.put(key.getNodeValue(), null);
						}
						child2 = child2.getNextSibling();
					}
				} else if ( XML_ROUTES_LABEL.equals(child.getNodeName()) ) {
					Node child2 = child.getFirstChild();
					while ( child2 != null ) {
						if ( XML_ROUTE_LABEL.equals(child2.getNodeName()) ) {
							// create route object
							Route route = new Route(null, new LinkedList<Modifier>(), new LinkedList<Output>());
							routes.add(route);
							// add the route to the input route mapping
							String inputKey = child2.getAttributes().getNamedItem(XML_INPUTKEY_ATTRIBUTE).getNodeValue();
							if ( inputRoutesMapping.containsKey(inputKey) == false ) {
								inputRoutesMapping.put(inputKey, new LinkedList<Route>() );	
							}
							inputRoutesMapping.get(inputKey).add(route);							
							
							Node child3 = child2.getFirstChild();
							while ( child3 != null ) {
								if ( XML_MODIFIERS_LABEL.equals(child3.getNodeName())) {
									Node child4 = child3.getFirstChild();
									
									while ( child4 != null ) {
										if ( XML_MODIFIER_LABEL.equals(child4.getNodeName())) {
											String modifierKey = child4.getAttributes().getNamedItem(XML_KEY_ATTRIBUTE).getNodeValue();
											if ( modifierRoutesMapping.containsKey(modifierKey) == false ) {
												modifierRoutesMapping.put(modifierKey, new LinkedList<Route>());
											}
											modifierRoutesMapping.get(modifierKey).add(route);
										}
										child4 = child4.getNextSibling();
									}

								} else if ( XML_OUTPUTS_LABEL.equals(child3.getNodeName())) {
									Node child4 = child3.getFirstChild();
									while ( child4 != null ) {
										
										if ( XML_OUTPUT_LABEL.equals(child4.getNodeName()) ) {
											
											String outputKey = child4.getAttributes().getNamedItem(XML_KEY_ATTRIBUTE).getNodeValue();
											if ( outputRoutesMapping.containsKey(outputKey) == false ) {
												outputRoutesMapping.put(outputKey, new LinkedList<Route>());
											}
											outputRoutesMapping.get(outputKey).add(route);
											
											Node child5  = child4.getFirstChild();
											while ( child5 != null ) {
												if ( XML_MODIFIERS_LABEL.equals(child5.getNodeName()) ) {
													Node child6 = child5.getFirstChild();
													
													while ( child6 != null ) {
														if ( XML_MODIFIER_LABEL.equals(child6.getNodeName()) ) {
															String modifierKey = child6.getAttributes().getNamedItem(XML_KEY_ATTRIBUTE).getNodeValue();
															if ( modifierOutputsMapping.containsKey(modifierKey) == false ) {
																modifierOutputsMapping.put(modifierKey, new LinkedList<String>());
															}
															modifierOutputsMapping.get(modifierKey).add(outputKey);
														}
														child6 = child6.getNextSibling();
													}												
													break;
												}
												child5 = child5.getNextSibling();
											}
										}
										
										child4 = child4.getNextSibling();
									}
								}
								child3 = child3.getNextSibling();
							}
						}
						child2 = child2.getNextSibling();
					}
				} else if ( XML_PARAMETERS_LABEL.equals(child.getNodeName()) ) {
					Node child2 = child.getFirstChild();
					while ( child2 != null ) {
						if ( XML_PARAMETER_LABEL.equals(child2.getNodeName())) {
							// load parameter
							NamedNodeMap attributes = child2.getAttributes();
							Node key = attributes.getNamedItem(XML_KEY_ATTRIBUTE);
							Node value = attributes.getNamedItem(XML_VALUE_ATTRIBUTE);
							
							if ( key != null && "".equals(key.getNodeValue()) == false ) {
								parametersMap.put(key.getNodeValue(), value.getNodeValue());
							}
						}
						child2 = child2.getNextSibling();
					}			
				}
				
				child = child.getNextSibling();
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	public void registerInput(String key, Input input) {
		this.inputMap.put(key, input);
		// load the parameters
		input.setParameters(loadParametersMap(parametersMappingMap.get(key)));
		// set the key
		input.setKey(key);
		
		// if the input is active
		if ( inputRoutesMapping.containsKey(key) ) {
			for( Route route : inputRoutesMapping.get(key) ) {
				route.setInput(input);
			}
		}
		// if the input is passive
		if ( passiveInputMappingMap.containsKey(key) ) {
			for ( String activeInputKey : passiveInputMappingMap.get(key) ) {
				if ( inputMap.containsKey(activeInputKey) ) {
					ActiveInput activeInput = (ActiveInput) inputMap.get(activeInputKey);
					activeInput.addPassiveInput((PassiveInput) input);
				}
			}
		}	
		
	}
	
	public void registerOutput(String key, Output output) {
		this.outputMap.put(key, output);
		// search for routes having this output key and add the output to the output list
		for (Route route : outputRoutesMapping.get(key)) {
			route.getOutputsList().add(output);			
		}
	}
	
	public void registerModifier(String key, Modifier modifier) {
		this.modifierMap.put(key, modifier);

		if ( modifierRoutesMapping.containsKey(key) ) {
			for ( Route route : modifierRoutesMapping.get(key) ) {
				route.getModifiersList().add(modifier);
			}
		}
		
		if ( modifierOutputsMapping.containsKey(key) ) {
			for ( String outputKey : modifierOutputsMapping.get(key) ) {
				if ( outputMap.containsKey(outputKey) ) {
					outputMap.get(outputKey).getModifierList().add(modifier);
				}
			}
		}			
	}
	
	public List<Route> getRoutes() {
		return routes;
	}
	
	private Map<String, String> loadParametersMap(String keys) {
		Map<String, String> returnMap = new HashMap<String, String>();
		// parse the keys string and load ( if found ) the parameters ... parameters which are not found are given null references
		if ( keys != null ) {
			StringTokenizer stok = new StringTokenizer(keys, ",");
			while ( stok.hasMoreTokens() ) {
				String key = stok.nextToken();
				if ( key.contains(":") ) {
					StringTokenizer stok2 = new StringTokenizer(key, ":");
					String mkey = stok2.nextToken();
					String vkey = stok2.nextToken();  
					returnMap.put( vkey, parametersMap.get(mkey) );
				} else {
					returnMap.put(key, parametersMap.get(key));
				}
			}
		}
		return returnMap;
	}
			
}